자바 컴파일러
1. 개요
1. 개요
자바 컴파일러는 프로그래밍 언어 자바를 위한 컴파일러이다. 이 컴파일러의 가장 일반적인 역할은 개발자가 작성한 소스 코드를 플랫폼 중립적인 자바 바이트코드로 변환하는 것이다. 변환된 바이트코드는 자바 클래스 파일이라는 특정 형식으로 저장되며, 이 파일은 다양한 운영체제에서 자바 가상 머신(JVM)에 의해 실행될 수 있다.
대부분의 자바 컴파일러는 컴파일 타임에 복잡한 최적화를 수행하지 않는다. 대신, 기본적인 변환 작업을 마친 바이트코드의 최적화는 런타임 시점에 JVM의 책임으로 남겨진다. JVM은 클래스 파일을 로드한 후, 바이트코드를 인터프리터 방식으로 해석하거나 JIT 컴파일 방식을 통해 네이티브 코드로 컴파일하면서 상황에 맞는 동적 최적화를 수행한다.
자바 컴파일러의 대표적인 구현체는 오라클의 자바 개발 키트(JDK)에 포함된 표준 컴파일러인 Javac이다. 역사적으로는 GCJ(GNU Compiler for Java)처럼 바이트코드가 아닌 특정 하드웨어와 운영체제 조합에 최적화된 네이티브 기계어를 직접 출력하는 컴파일러도 존재했다. 또한, 자바 컴파일러와 프로그래밍적으로 상호작용하는 방법에 대한 표준은 자바 커뮤니티 프로세스의 JSR 199에 규정되어 있다.
2. 역사
2. 역사
자바 컴파일러의 역사는 자바 언어 자체의 탄생과 함께 시작된다. 1990년대 초, 제임스 고슬링을 포함한 썬 마이크로시스템즈의 연구원들은 그린 프로젝트를 통해 다양한 가전제품에 임베디드될 소프트웨어를 개발하기 위한 새로운 언어를 구상했다. 이 과정에서 탄생한 오크 언어가 후에 자바로 발전했으며, 이를 위한 최초의 컴파일러가 만들어졌다. 초기 자바 컴파일러는 C++로 작성되었으며, 소스 코드를 플랫폼 독립적인 중간 형태인 자바 바이트코드로 변환하는 방식을 채택했다. 이 설계는 "한 번 작성하고, 어디서나 실행된다"는 자바의 핵심 철학을 실현하는 기반이 되었다.
자바가 1995년에 공식 발표된 이후, 썬 마이크로시스템즈의 자바 개발 키트(JDK)에 포함된 표준 컴파일러인 Javac가 사실상의 표준이 되었다. 시간이 지나면서 자바 생태계가 성장함에 따라 다양한 목적과 성능 요구를 충족시키기 위한 대체 컴파일러 구현체들이 등장하기 시작했다. 예를 들어, 자유 소프트웨어 재단의 GNU 컴파일러 모음(GCC) 프로젝트의 일부로 개발된 GCJ는 자바 소스 코드나 바이트코드를 직접 네이티브 코드로 컴파일할 수 있는 독특한 접근 방식을 제공했다. 또한, 이클립스 IDE의 핵심 컴파일러인 ECJ는 증분 컴파일과 높은 수준의 오류 검사 기능으로 개발자 생산성에 중점을 두었다.
자바 플랫폼이 진화하면서 컴파일러의 역할과 통합 방식도 발전해왔다. 자바 커뮤니티 프로세스를 통해 JSR 199이 제정되어 자바 컴파일러와 프로그래밍적으로 상호작용하는 표준 API가 마련되었다. 이는 통합 개발 환경이나 빌드 도구가 컴파일러를 내부적으로 호출하고 진단 정보를 얻는 방식을 표준화하는 데 기여했다. 한편, Javac는 오픈JDK 프로젝트의 일부가 되어 그 소스 코드가 공개되었으며, 지속적인 개선을 통해 자바 8의 람다 표현식이나 자바 9의 모듈 시스템 같은 새로운 언어 기능을 지원하도록 진화하고 있다.
3. 작동 방식
3. 작동 방식
3.1. 소스 코드에서 바이트코드로
3.1. 소스 코드에서 바이트코드로
자바 컴파일러의 핵심 작업은 개발자가 작성한 자바 소스 코드를 자바 가상 머신(JVM)이 이해하고 실행할 수 있는 자바 바이트코드로 변환하는 것이다. 이 과정은 일반적으로 컴파일 타임에 이루어진다. 컴파일러는 먼저 소스 코드의 문법과 구조를 분석하여 구문 트리(AST)를 생성하고, 의미상의 오류를 검사한 후, 최종적으로 .class 확장자를 가진 자바 클래스 파일을 생성한다.
이렇게 생성된 바이트코드는 특정 하드웨어나 운영체제에 종속되지 않는 중간 형태이다. 이는 자바의 핵심 철학인 "한 번 작성하고, 어디서나 실행한다"(Write Once, Run Anywhere)를 실현하는 기반이 된다. Javac와 같은 표준 컴파일러는 실행 속도를 위한 복잡한 최적화를 크게 수행하지 않으며, 이는 주로 JIT 컴파일러에 의한 런타임 최적화로 미룬다.
주류 자바 컴파일러의 출력은 네이티브 기계어가 아닌 바이트코드이지만, GCJ(GNU Compiler for Java)와 같은 예외적인 구현체도 존재했다. GCJ는 자바 소스 코드나 바이트코드를 직접 네이티브 코드로 컴파일할 수 있었으나, 현재는 개발이 중단된 상태이다.
3.2. 클래스 파일
3.2. 클래스 파일
자바 컴파일러가 소스 코드를 번역한 최종 산출물은 자바 클래스 파일이다. 이 파일은 .class 확장자를 가지며, 플랫폼 중립적인 자바 바이트코드를 담고 있는 컨테이너 역할을 한다. 클래스 파일의 구조는 자바 가상 머신 명세에 엄격하게 정의되어 있어, 어떤 운영체제나 하드웨어에서도 동일한 파일 형식을 유지한다. 이는 자바의 핵심 철학인 "한 번 작성하면, 어디서나 실행된다"는 원칙을 실현하는 기반이 된다.
클래스 파일은 마법 넘버, 버전 정보, 상수 풀, 접근 플래그, 필드 및 메서드 정보, 속성 테이블 등으로 구성된 정밀한 바이너리 형식을 따른다. 특히 상수 풀은 클래스, 메서드, 필드 이름, 문자열 리터럴, 숫자 상수 등 모든 심볼릭 레퍼런스를 저장하는 공유 풀로, 메모리 효율성과 런타임 해석 속도에 중요한 역할을 한다. 이 구조 덕분에 JVM은 클래스 파일을 로드할 때 빠르게 필요한 정보를 찾아 링크하고 실행할 수 있다.
클래스 파일은 자바 컴파일러인 Javac에 의해 생성되는 것이 일반적이지만, 코틀린이나 스칼라 같은 다른 JVM 언어의 컴파일러, 또는 바이트코드 조작 라이브러리에 의해서도 생성되거나 수정될 수 있다. 생성된 클래스 파일은 자바 아카이브(JAR) 파일이나 WAR 파일과 같은 배포 단위로 패키징되어 실제 애플리케이션을 구성한다. 따라서 클래스 파일은 소스 코드와 실행 가능한 프로그램 사이의 중간 표현이자, JVM이 이해할 수 있는 유일한 실행 형식이다.
4. 주요 구현체
4. 주요 구현체
4.1. Javac (오라클 JDK)
4.1. Javac (오라클 JDK)
Javac은 오라클의 자바 개발 키트(JDK)에 포함된 표준 자바 컴파일러이다. 이 컴파일러는 자바 언어의 참조 구현체로서, 자바 소스 코드(.java 파일)를 플랫폼 중립적인 자바 바이트코드를 담은 자바 클래스 파일(.class 파일)로 변환하는 역할을 한다. Javac은 JDK의 핵심 구성 요소이며, 대부분의 자바 개발 환경에서 기본 컴파일러로 사용된다.
Javac의 주요 설계 목표는 언어 사양을 정확하게 준수하는 바이트코드를 생성하는 데 있다. 따라서 대부분의 고수준 최적화는 컴파일러가 아닌 자바 가상 머신(JVM)의 JIT 컴파일러에 맡겨진다. 이는 "한 번 작성하면 어디서나 실행된다"는 자바의 철학과 맞닿아 있으며, 런타임 시 대상 플랫폼에 맞춘 최적화를 가능하게 한다.
이 컴파일러는 명령줄 도구로 사용할 수 있을 뿐만 아니라, 자바 컴파일러 API(JSR 199)를 통해 프로그래밍적으로 호출하여 소스 코드를 동적으로 컴파일하고 분석하는 데에도 활용할 수 있다. Javac의 소스 코드는 오픈JDK 프로젝트를 통해 공개되어 있으며, 이는 다른 자바 개발 도구나 대체 컴파일러 구현의 기반이 되기도 한다.
4.2. GCJ (GNU Compiler for Java)
4.2. GCJ (GNU Compiler for Java)
GCJ는 자유 소프트웨어 재단의 GNU 프로젝트의 일환으로 개발된 자바 컴파일러이다. 이 컴파일러는 GCC(GNU Compiler Collection)에 통합되어 제공되었으며, 자바 소스 코드를 자바 바이트코드가 아닌 직접적인 네이티브 코드(기계어)로 컴파일할 수 있는 능력이 주요 특징이었다. 이를 통해 자바 프로그램을 자바 가상 머신(JVM) 없이도 독립 실행형 애플리케이션으로 구동할 수 있었다.
GCJ의 개발 목적은 자바 애플리케이션을 완전한 자유 소프트웨어 생태계 내에서 실행 가능하게 하는 것이었다. 이는 당시 썬 마이크로시스템즈의 자바 구현체가 완전한 자유 소프트웨어 라이선스를 갖지 않았던 상황에서 중요한 의미를 가졌다. GCJ는 자바 언어 사양의 상당 부분을 지원했으며, 자바 표준 라이브러리의 대체 구현체인 GNU Classpath와 함께 작동하도록 설계되었다.
그러나 GCJ 프로젝트는 2017년을 기점으로 공식적으로 개발이 중단되었다. 이는 오라클이 Javac 컴파일러와 OpenJDK 프로젝트를 완전한 오픈 소스로 공개하는 등 자바 생태계의 라이선스 환경이 크게 변화했기 때문이다. 결과적으로, 자바를 네이티브 코드로 컴파일하는 접근 방식은 현재는 그라일VM과 같은 다른 현대적인 기술로 계승되었다.
4.3. ECJ (Eclipse Compiler for Java)
4.3. ECJ (Eclipse Compiler for Java)
ECJ는 이클립스 재단이 개발하고 유지 관리하는 자바 컴파일러이다. 이 컴파일러는 이클립스 IDE의 핵심 구성 요소로, IDE 내에서 실시간으로 소스 코드를 검사하고 컴파일하는 데 사용된다. Javac와 같은 표준 컴파일러와 달리, ECJ는 증분 컴파일을 지원하여 변경된 파일만 빠르게 재컴파일함으로써 대규모 프로젝트에서 개발 생산성을 크게 향상시킨다.
ECJ의 주요 특징은 높은 수준의 자바 언어 사양 준수와 엄격한 코드 검증이다. 이 컴파일러는 자바 커뮤니티 프로세스에서 정의된 자바 컴파일러 API (JSR 199)를 구현하여, 자바 개발 키트에 포함된 Javac와 호환되는 방식으로 프로그래밍적으로 호출될 수 있다. 또한, ECJ는 자바 바이트코드를 직접 생성하는 대신 추상 구문 트리를 자바 가상 머신이 이해할 수 있는 클래스 파일로 변환하는 역할을 수행한다.
ECJ는 독립형 컴파일러로도 사용할 수 있으며, 안드로이드 스튜디오의 초기 버전을 포함한 여러 빌드 도구와 통합 개발 환경에서 채택되었다. 이 컴파일러의 설계는 모듈화와 확장성에 중점을 두어, 플러그인 아키텍처를 통해 새로운 언어 기능이나 정적 분석 도구를 쉽게 통합할 수 있도록 한다.
5. 컴파일러 API (JSR 199)
5. 컴파일러 API (JSR 199)
자바 컴파일러 API는 자바 컴파일러와 프로그래밍적으로 통신하는 방법을 정의한 표준이다. 이 표준은 자바 커뮤니티 프로세스의 JSR 199에 의해 규정된다. 이 API는 자바 개발 키트 6(코드명 Mustang)부터 표준 라이브러리에 포함되어 제공되기 시작했다.
이 API의 주요 목적은 자바 소스 코드를 자바 바이트코드로 컴파일하는 과정을 외부 프로그램에서 제어하고 모니터링할 수 있도록 하는 것이다. 예를 들어, 통합 개발 환경(IDE)이나 빌드 도구가 Javac와 같은 컴파일러를 내부적으로 호출하고, 컴파일 오류와 경고 메시지를 수집하며, 컴파일 작업을 비동기적으로 수행하는 데 이 API를 사용한다.
컴파일러 API의 핵심 인터페이스는 JavaCompiler이다. 이 인터페이스를 통해 애플리케이션은 소스 파일 목록을 지정하고, 컴파일러 옵션을 설정하며, 컴파일 작업을 실행할 수 있다. 또한 DiagnosticListener 인터페이스를 구현하여 컴파일 과정에서 발생하는 상세한 진단 정보를 실시간으로 받아볼 수도 있다. 이를 통해 개발 도구는 사용자에게 더 풍부한 피드백을 제공할 수 있다.
이 표준 API의 등장으로, 애플리케이션은 특정 컴파일러 구현(예: 오라클의 Javac 또는 이클립스의 ECJ)에 종속되지 않고 일관된 방식으로 컴파일 서비스를 이용할 수 있게 되었다. 이는 자바 생태계 내에서 도구의 상호운용성을 크게 향상시킨 중요한 발전이었다.
6. 최적화
6. 최적화
6.1. 컴파일 타임 최적화
6.1. 컴파일 타임 최적화
자바 컴파일러의 컴파일 타임 최적화는 일반적으로 제한적이다. 대부분의 자바 컴파일러, 특히 표준 컴파일러인 Javac는 코드를 자바 바이트코드로 변환하는 과정에서 복잡한 최적화를 거의 수행하지 않는다. 이는 최적화의 대부분을 자바 가상 머신(JVM)의 런타임 시점으로 미루기 위한 설계 철학에 따른 것이다. JVM은 프로그램 실행 시 동적으로 코드를 분석하고, 실제 사용 패턴에 기반하여 JIT 컴파일과 같은 고도화된 최적화를 수행할 수 있다.
컴파일 타임에 이루어지는 최적화는 주로 상수 접기, 데드 코드 제거, 기본적인 인라인 확장과 같은 비교적 간단한 수준에 머문다. 예를 들어, final로 선언된 상수 표현식은 컴파일 시 계산되어 결과값으로 대체된다. 또한, 도달할 수 없는 코드나 사용되지 않는 지역 변수는 제거될 수 있다. 이러한 최적화는 코드 크기를 줄이고 약간의 성능 향상을 가져올 수 있지만, 그 범위와 효과는 제한적이다.
이러한 접근 방식의 이점은 컴파일된 자바 클래스 파일이 특정 하드웨어나 운영체제에 종속되지 않고, 다양한 JVM 구현체에서 실행될 수 있는 높은 이식성을 유지한다는 점이다. 복잡한 최적화는 각 플랫폼의 특성을 가장 잘 아는 JVM이 런타임에 수행하도록 위임함으로써 최적의 성능을 달성한다. 따라서 자바의 성능 모델은 컴파일러와 JVM이 협력하는 2단계 시스템으로 이해된다.
6.2. 런타임 최적화 (JIT 컴파일)
6.2. 런타임 최적화 (JIT 컴파일)
자바 컴파일러가 생성한 자바 바이트코드는 자바 가상 머신(JVM) 위에서 실행된다. JVM은 이 바이트코드를 실행하는 과정에서 성능을 극대화하기 위해 런타임 최적화를 수행하는데, 그 핵심 기술이 JIT 컴파일(Just-In-Time Compilation)이다. JIT 컴파일은 프로그램 실행 중에 자주 사용되는 바이트코드 부분(핫스팟)을 실시간으로 해당 플랫폼의 네이티브 코드(기계어)로 변환하고 최적화한다.
이 방식은 전통적인 AOT 컴파일(Ahead-Of-Time Compilation)과 다르다. AOT 컴파일은 실행 전에 모든 코드를 미리 네이티브 코드로 변환하는 반면, JIT 컴파일은 프로그램 실행 중에 필요한 부분만 변환한다. 이로 인해 초기 실행 시 약간의 지연(웜업 시간)이 발생할 수 있지만, 런타임 정보를 활용한 더 정교한 최적화가 가능하다는 장점이 있다. 예를 들어, 특정 메서드가 매우 자주 호출된다는 사실을 런타임에 감지하면, 해당 메서드에 대해 공격적인 인라인 확장이나 루프 언롤링 같은 최적화를 적용할 수 있다.
대표적인 JVM 구현체인 오라클의 HotSpot JVM은 이름 그대로 '핫스팟' 감지 및 JIT 컴파일 기술로 유명하다. OpenJDK 프로젝트의 일부인 이 JVM은 인터프리터 모드로 코드를 실행하다가, 특정 코드 경로의 실행 빈도가 임계값을 넘으면 백그라운드에서 해당 코드를 최적화된 네이티브 코드로 컴파일한다. 이후부터는 인터프리팅을 중단하고 훨씬 빠른 네이티브 코드를 실행하여 전체적인 성능을 향상시킨다. 이처럼 자바의 높은 성능은 컴파일 타임이 아닌 런타임에, JVM의 JIT 컴파일러에 의해 크게 좌우된다.
7. 관련 기술
7. 관련 기술
7.1. 자바 가상 머신 (JVM)
7.1. 자바 가상 머신 (JVM)
자바 가상 머신은 자바 프로그램의 실행을 담당하는 핵심 런타임 환경이다. 자바 컴파일러가 생성한 플랫폼 중립적인 자바 바이트코드를 특정 운영체제와 하드웨어에서 실행 가능한 형태로 해석하거나 컴파일한다. 이는 "Write Once, Run Anywhere"라는 자바의 핵심 철학을 실현하는 기반 기술이다.
JVM의 주요 역할은 클래스 로더를 통해 자바 클래스 파일을 메모리에 로드하고, 바이트코드를 검증하며, 실행 엔진을 통해 실제 명령을 수행하는 것이다. 실행 엔진은 초기에는 인터프리터 방식으로 동작했으나, 성능 향상을 위해 JIT 컴파일러를 도입하여 자주 실행되는 코드를 런타임에 네이티브 기계어로 컴파일한다. 또한 가비지 컬렉션을 통해 자동 메모리 관리를 수행한다.
JVM은 단일 기술이 아닌 하나의 명세서이다. 오라클의 HotSpot이 대표적 구현체이며, OpenJDK, Eclipse OpenJ9, GraalVM 등 다양한 벤더의 구현체가 존재한다. 이러한 JVM 구현체들은 자바 프로그램의 실행뿐만 아니라 코틀린, 스칼라, 그루비 같은 다른 JVM 언어들도 실행할 수 있는 플랫폼 역할을 한다.
7.2. 자바 바이트코드
7.2. 자바 바이트코드
자바 바이트코드는 자바 컴파일러가 자바 소스 코드를 변환하여 생성하는 중간 표현 형식이다. 이는 자바 가상 머신(JVM)이 이해하고 실행할 수 있는 명령어 집합으로 구성된다. 바이트코드는 특정 하드웨어나 운영체제에 종속되지 않는 플랫폼 중립적 특징을 가지며, 이 덕분에 자바의 핵심 원칙인 "한 번 작성하고, 어디서나 실행한다"(Write Once, Run Anywhere)를 실현하는 기반이 된다.
컴파일러에 의해 생성된 바이트코드는 자바 클래스 파일(.class 확장자)에 저장된다. 이 클래스 파일은 JVM이 로드하고 실행할 수 있는 표준화된 구조를 따른다. 바이트코드는 사람이 읽을 수 있는 형태는 아니지만, 자바 디스어셈블러 도구를 사용하면 그 내용을 분석할 수 있다.
JVM은 이 바이트코드를 실행할 때, 인터프리터 방식으로 직접 해석하거나, JIT 컴파일러를 통해 해당 실행 환경의 네이티브 코드(기계어)로 변환하여 실행한다. 대부분의 자바 컴파일러는 컴파일 시간에 복잡한 최적화를 수행하지 않고, 이러한 최적화 작업을 JVM의 런타임 시점으로 미룬다. 이는 프로그램이 실제 실행되는 환경 정보를 바탕으로 더 효과적인 최적화를 할 수 있기 때문이다.
7.3. 자바 개발 키트 (JDK)
7.3. 자바 개발 키트 (JDK)
자바 개발 키트는 자바 애플리케이션을 개발하고 실행하기 위한 핵심 소프트웨어 패키지이다. JDK는 자바 컴파일러, 자바 가상 머신, 그리고 다양한 개발 도구와 라이브러리를 포함한다. 이 키트는 자바 소스 코드를 자바 바이트코드로 변환하는 Javac 컴파일러를 제공하며, 이 바이트코드는 플랫폼 중립적인 자바 클래스 파일 형태로 출력된다. JDK 없이는 자바 프로그래밍이 불가능하다고 할 수 있을 정도로 개발의 기초를 이룬다.
JDK의 핵심 구성 요소 중 하나는 자바 런타임 환경이다. JRE는 자바 애플리케이션을 실행하는 데 필요한 자바 가상 머신과 핵심 클래스 라이브러리만을 포함하지만, JDK는 여기에 개발 도구를 추가로 포함한다. 이러한 도구에는 소스 코드 컴파일러, 문서 생성기, 디버거, 아카이브 도구 등이 있다. JDK는 오라클이 주로 배포하지만, 오픈소스 구현체인 OpenJDK도 널리 사용된다.
JDK는 자바 플랫폼의 표준 에디션 외에도 엔터프라이즈 에디션, 마이크로 에디션 등 다양한 버전으로 제공되어 서버, 모바일, 임베디드 시스템 등 다양한 환경에서의 개발을 지원한다. 또한, JDK 내부의 컴파일러와 도구들은 자바 커뮤니티 프로세스를 통해 정의된 표준을 따르며, 지속적으로 성능과 보안이 개선되고 있다.
8. 여담
8. 여담
자바 컴파일러의 주요 구현체인 Javac는 오라클의 자바 개발 키트(JDK)에 포함된 표준 컴파일러이다. 이 외에도 자유 소프트웨어 재단의 GNU 컴파일러 모음(GCC) 프로젝트의 일부였던 GCJ(GNU Compiler for Java)와 같이 자바 소스 코드를 직접 네이티브 코드로 변환하는 컴파일러도 개발된 바 있다. 그러나 GCJ는 현재 개발이 중단된 상태이다.
이클립스 재단의 이클립스 통합 개발 환경(IDE)에 내장된 ECJ(Eclipse Compiler for Java)는 증분 컴파일과 같은 고급 기능을 지원하는 대표적인 대체 컴파일러이다. 자바 컴파일러와 프로그래밍적으로 상호작용하기 위한 표준 인터페이스는 자바 커뮤니티 프로세스의 JSR 199에 의해 정의되었다.
자바 컴파일러의 일반적인 출력물은 플랫폼 중립적인 자바 바이트코드를 담은 자바 클래스 파일이다. 대부분의 컴파일러는 복잡한 최적화를 수행하지 않고, 이 작업은 프로그램 실행 시 자바 가상 머신(JVM)의 JIT 컴파일러에 의해 런타임에 이루어진다. 이러한 설계는 플랫폼 독립성과 동적 최적화의 이점을 제공한다.
